﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="With_WeakEventManager.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the With_WeakEventManager type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Reactive.Linq;
    using System.Reactive.Subjects;
    using Assets;
    using Collections;
    using ComponentModel;

#if SILVERLIGHT
    using Microsoft.Silverlight.Testing;
#else
    using Testing;
#endif

    using Microsoft.VisualStudio.TestTools.UnitTesting;
    

    [TestClass, Tag("WeakEventManager")]
    public class With_WeakEventManager
    {
        private const int SubscriberCount = 10;
        public static PropertyChangedEventArgs DefaultPropertyChangedArgs = new PropertyChangedEventArgs(string.Empty);
        private WeakEventManager SystemUnderTest { get; set; }

        public CollectableCollection<Publisher> Publishers { get; set; }

        public CollectableCollection<Subscriber> Subscribers { get; set; }

        [TestInitialize]
        public virtual void TestInitialize()
        {
            SystemUnderTest = new WeakEventManager();
            Subscribers = new CollectableCollection<Subscriber>(SubscriberCount, () => new Subscriber());
            Publishers = new CollectableCollection<Publisher>(2, () => new Publisher());
        }

        public With_WeakEventManager WhenSubscribersToPropertyChangedHaveBeenRemoved(Publisher publisher,
            IEnumerable<Subscriber> subscribers)
        {
            subscribers.ForEach(subscriber =>
                SystemUnderTest.RemoveHandler(publisher, DataEventBinders.PropertyChanged, subscriber.OnInvokeAction));
            return this;
        }

        public With_WeakEventManager WhenSubscribersToPropertyChangedHaveBeenAdded(Publisher publisher, IEnumerable<Subscriber> subscribers)
        {
            subscribers.ForEach(subscriber =>
                SystemUnderTest.AddHandler(publisher, DataEventBinders.PropertyChanged, subscriber.OnInvokeAction));
            return this;
        }

        public With_WeakEventManager WhenPropertyChangedRaised(Publisher publisher, PropertyChangedEventArgs args)
        {
            publisher.RaisePropertyChanged(args);
            return this;
        }

        public With_WeakEventManager WhenPublishersAreCollected(params int[] indices)
        {
            Publishers.Collect(indices);
            return this;
        }

        public With_WeakEventManager WhenSubscribersAreCollected(params int[] indices)
        {
            Subscribers.Collect(indices);
            return this;
        }

        #region Nested type: CollectableCollection

        #endregion

        #region Nested type: When_event_is_raised_and_all_subscribers_have_been_collected

        [TestClass, Tag("WeakEventManager")]
        public class When_event_is_raised_and_all_subscribers_have_been_collected : With_WeakEventManager
        {
            [TestInitialize]
            public override void TestInitialize()
            {
                base.TestInitialize();
                WhenSubscribersToPropertyChangedHaveBeenAdded(Publishers[0], Subscribers.Take(1))
                    .WhenSubscribersAreCollected(0)
                    .WhenPropertyChangedRaised(Publishers[0], new PropertyChangedEventArgs(string.Empty));
            }

            [TestMethod]
            public void Should_remove_event_source()
            {
                Assert.IsFalse(WeakEventManager.EventSource.SelectFrom(SystemUnderTest).Any());
            }

            [TestMethod]
            public void Should_have_null_delegate()
            {
                Assert.IsNull(SystemUnderTest.GetEvent(Publishers[0], DataEventBinders.PropertyChanged));
            }
        }

        #endregion

        #region Nested type: When_event_is_raised_and_some_but_not_all_subscribers_have_been_collected

        [TestClass, Tag("WeakEventManager")]
        public class When_event_is_raised_and_some_but_not_all_subscribers_have_been_collected : With_WeakEventManager
        {
            [TestInitialize]
            public override void TestInitialize()
            {
                base.TestInitialize();
                WhenSubscribersToPropertyChangedHaveBeenAdded(Publishers[0], Subscribers.Take(2))
                    .WhenSubscribersAreCollected(0)
                    .WhenPropertyChangedRaised(Publishers[0], DefaultPropertyChangedArgs);
            }

            [TestMethod]
            public void Should_not_remove_event_source()
            {
                Assert.IsTrue(WeakEventManager.EventSource.SelectFrom(SystemUnderTest).Any());
            }

            [TestMethod]
            public void Should_remove_delegate_from_invocation_list()
            {
                var theDelegate = SystemUnderTest
                    .GetEvent(Publishers[0], DataEventBinders.PropertyChanged);
                Assert.IsNotNull(theDelegate, "Expected non-null delegate");
                Assert.AreEqual(1, theDelegate.GetInvocationList(false).Count);
            }

            [TestMethod]
            public void Should_notify_remaining_subscribers()
            {
                Subscribers
                    .ElementAt(1)
                    .AssertSingleLogEntry(Publishers[0], DefaultPropertyChangedArgs);
            }
        }

        #endregion

        #region Nested type: When_event_is_raised_and_some_subscribers_have_been_removed

        [TestClass, Tag("WeakEventManager")]
        public class When_event_is_raised_and_some_subscribers_have_been_removed : With_WeakEventManager
        {
            public readonly PropertyChangedEventArgs Args = new PropertyChangedEventArgs(string.Empty);

            [TestInitialize]
            public override void TestInitialize()
            {
                base.TestInitialize();
                WhenSubscribersToPropertyChangedHaveBeenAdded(Publishers[0], Subscribers)
                    .WhenPropertyChangedRaised(Publishers[0], Args)
                    .WhenSubscribersToPropertyChangedHaveBeenRemoved(Publishers[0], Subscribers.Skip(5).Take(5))
                    .WhenPropertyChangedRaised(Publishers[0], Args);
            }

            [TestMethod]
            public void Should_only_notify_remaining_subscribers()
            {
                Subscribers
                    .Take(5)
                    .ForEach(x => Assert.AreEqual(2, x.LogEntries.Count));
                Subscribers
                    .Skip(5)
                    .Take(5)
                    .ForEach(x => Assert.AreEqual(1, x.LogEntries.Count));
            }
        }

        #endregion

        #region Nested type: When_event_is_raised_and_there_are_other_publishers_and_subscribers_using_the_same_binding

        [TestClass, Tag("WeakEventManager")]
        public class When_event_is_raised_and_there_are_other_publishers_and_subscribers_using_the_same_binding : With_WeakEventManager
        {
            public readonly PropertyChangedEventArgs Args = new PropertyChangedEventArgs(string.Empty);

            [TestInitialize]
            public override void TestInitialize()
            {
                base.TestInitialize();
                WhenSubscribersToPropertyChangedHaveBeenAdded(Publishers[0], Subscribers.Take(5))
                    .WhenSubscribersToPropertyChangedHaveBeenAdded(Publishers[1], Subscribers.Skip(5).Take(5))
                    .WhenPropertyChangedRaised(Publishers[0], Args);
            }

            [TestMethod]
            public void Should_only_notify_subscribers_attached_to_the_raising_publisher()
            {
                Subscribers
                    .Take(5)
                    .ForEach(x => x.AssertSingleLogEntry(Publishers[0], Args));
                Subscribers
                    .Skip(5)
                    .Take(5)
                    .ForEach(x => Assert.AreEqual(0, x.LogEntries.Count));
            }
        }

        #endregion

        #region Nested type: When_event_is_raised_and_there_are_subscribers

        [TestClass, Tag("WeakEventManager")]
        public class When_event_is_raised_and_there_are_subscribers : With_WeakEventManager
        {
            public readonly PropertyChangedEventArgs Args = new PropertyChangedEventArgs(string.Empty);

            [TestInitialize]
            public override void TestInitialize()
            {
                base.TestInitialize();
                WhenSubscribersToPropertyChangedHaveBeenAdded(Publishers[0], Subscribers)
                    .WhenPropertyChangedRaised(Publishers[0], Args);
            }

            [TestMethod]
            public void Should_notify_all_subscribers()
            {
                Subscribers.ForEach(x => x.AssertSingleLogEntry(Publishers[0], Args));
            }
        }

        #endregion

        #region Nested type: When_event_is_raised_on_observable

        [TestClass]
        public class When_event_is_raised_on_observable : With_WeakEventManager
        {
            [TestMethod]
            public void Should_reveive_notifications()
            {
                var subject = new Subject<string>();
                var observable = subject.AsObservable();

                SystemUnderTest.AddHandler(observable, observable.DefaultEventBinder(), Subscribers[0].OnInvokeAction);
                subject.OnNext("Message1");
                Subscribers[0].AssertSingleLogEntry("Message1");
            }
        }

        #endregion

        #region Nested type: When_event_is_raised_on_observable_and_subscriber_is_collected

        [TestClass]
        public class When_event_is_raised_on_observable_and_subscriber_is_collected : With_WeakEventManager
        {
            [TestMethod]
            public void Should_reveive_notifications()
            {
                // create fake observable and add subscriber
                var observable = new FakeObservable();
                SystemUnderTest.AddHandler(observable, observable.DefaultEventBinder(), Subscribers[0].OnInvokeAction);

                // collect the subscriber
                WhenSubscribersAreCollected(0);

                // send notification to the observer
                observable.Observer.OnNext("Test");

                // should have disposed the subscription on the observer
                Assert.AreEqual(1, observable.DisposeCount, "Subscription was not released on the observable.");
            }

            #region Nested type: FakeObservable

            public class FakeObservable : IObservable<string>
            {
                public int DisposeCount;
                public IObserver<string> Observer;

                #region IObservable<string> Members

                public IDisposable Subscribe(IObserver<string> observer)
                {
                    Observer = observer;
                    return new FakeDisposable(this);
                }

                #endregion

                #region Nested type: FakeDisposable

                public class FakeDisposable : IDisposable
                {
                    private readonly FakeObservable _observable;

                    public FakeDisposable(FakeObservable observable)
                    {
                        _observable = observable;
                    }

                    #region IDisposable Members

                    public void Dispose()
                    {
                        _observable.DisposeCount++;
                    }

                    #endregion
                }

                #endregion
            }

            #endregion
        }

        #endregion

        #region Nested type: When_more_capacity_is_required_and_all_subscribers_to_an_event_source_have_been_collected

        [TestClass, Tag("WeakEventManager")]
        public class When_more_capacity_is_required_and_all_subscribers_to_an_event_source_have_been_collected : With_WeakEventManager
        {
            [TestInitialize]
            public override void TestInitialize()
            {
                base.TestInitialize();
                WhenSubscribersToPropertyChangedHaveBeenAdded(Publishers[0], Subscribers.Take(1))
                    .WhenSubscribersAreCollected(0);
            }

            private int AddEventSourcesAndCollectUntil(Func<IEnumerable<WeakEventManager.EventSource>, bool> predicate,
                int maxIterations = 10)
            {
                var publishers = new List<Publisher>();
                for (var i = 0; i < maxIterations; i++)
                {
                    // add publisher (placing in list so we maintain a strong reference)
                    var publisher = new Publisher();
                    publishers.Add(publisher);

                    WhenSubscribersToPropertyChangedHaveBeenAdded(publisher, Subscribers.Where(x => x != null).Take(1));

                    GC.Collect();
                    if (predicate(WeakEventManager.EventSource.SelectFrom(SystemUnderTest)))
                    {
                        return i;
                    }
                }

                return -1;
            }

            [TestMethod]
            public void Should_remove_event_sources_for_collected_publishers()
            {
                Assert.AreNotEqual(-1,
                    AddEventSourcesAndCollectUntil(x => !x.Any(source => source.Publisher == Publishers[0])));
            }
        }

        #endregion

        #region Nested type: When_more_capacity_is_required_and_some_publishers_have_been_collected

        [TestClass, Tag("WeakEventManager")]
        public class When_more_capacity_is_required_and_some_publishers_have_been_collected : With_WeakEventManager
        {
            [TestInitialize]
            public override void TestInitialize()
            {
                base.TestInitialize();
                WhenSubscribersToPropertyChangedHaveBeenAdded(Publishers[0], Subscribers.Take(1))
                    .WhenPublishersAreCollected();
            }

            private int AddEventSourcesUntil(Func<IEnumerable<WeakEventManager.EventSource>, bool> predicate, int maxIterations = 10)
            {
                var publishers = new List<Publisher>();
                for (var i = 0; i < maxIterations; i++)
                {
                    // add publisher (placing in list so we maintain a strong reference)
                    var publisher = new Publisher();
                    publishers.Add(publisher);

                    WhenSubscribersToPropertyChangedHaveBeenAdded(publisher, Subscribers.Take(1));

                    GC.Collect();
                    if (predicate(WeakEventManager.EventSource.SelectFrom(SystemUnderTest)))
                    {
                        return i;
                    }
                }

                return -1;
            }

            [TestMethod]
            public void Should_remove_event_sources_whose_publishers_have_been_collected()
            {
                Assert.AreNotEqual(-1, AddEventSourcesUntil(x => !x.Any(source => source.Publisher == null)));
            }
        }

        #endregion

        #region Nested type: When_using_equivalent_event_binders

        [TestClass, Tag("WeakEventManager")]
        public class When_using_equivalent_event_binders : With_WeakEventManager
        {
            public readonly PropertyChangedEventArgs PropertyChangedEventArgs = new PropertyChangedEventArgs("Test");

            [TestInitialize]
            public override void TestInitialize()
            {
                base.TestInitialize();
                SystemUnderTest.AddHandler(Publishers[0], DataEventBinders.PropertyChanged, Subscribers[0].OnInvokeAction);
                var binder = new EventBinder<INotifyPropertyChanged, PropertyChangedEventHandler>(
                    "PropertyChanged", (p, h) => p.PropertyChanged += h, (p, h) => p.PropertyChanged -= h);
                SystemUnderTest.AddHandler(Publishers[0], binder, Subscribers[1].OnInvokeAction);

                WhenPropertyChangedRaised(Publishers[0], PropertyChangedEventArgs);
            }

            [TestMethod]
            public void Should_notify()
            {
                Subscribers.Take(2).ForEach(x => x.AssertSingleLogEntry(Publishers[0], PropertyChangedEventArgs));
            }
        }

        #endregion


    }
}